package com.example.guard_corelibrary;


import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import com.example.guard_corelibrary.multidex.MultiDex;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;


/**
 * 使用了这个library，相当于使用了dex分包，multidex
 * 1.如果dex加密了，则必须是最先运行这个ProxyApp，
 * 应为只有这个class没有加密（可以运行），进行解密操作 后续才能工作
 * 2.如果dex没有加密，这个又是最先运行的Application，即解密失败
 * 则直接使用源文件作为dex
 * <p>
 * Created by tys on 2018/2/16.
 */

public class ProxyApp extends Application {
    private Application realApplication;
    private String className;
    private boolean isBind;
    private boolean isGuard;

    @Override
    public void onCreate() {
        super.onCreate();
        //到这里时，Manifest 中配置的Application已经创建完成 并且执行了一些操作 ActivityThread -> handleBindApplication
        try {
            bindApplicationContext();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //application创建之后  onCreate()之前
    private void bindApplicationContext() throws Exception {
        if (isBind)
            return;

        //构建真正的Application
        if (TextUtils.isEmpty(className))
            return;
        //Instrumentation -> newApplication 中也是通过反射创建的Application对象
        Class<?> realAppCls = Class.forName(className);
        realApplication = (Application) realAppCls.newInstance();

        //这个就是attachBaseContext传进来的 ContextImpl
        Context mBase = getBaseContext();

        //调用真正Application 的attach(Context content)方法
        Method attachMethod = Application.class.getDeclaredMethod("attach", Context.class);
        attachMethod.setAccessible(true);
        attachMethod.invoke(realApplication, mBase);

        //1.ContextImpl -> setOuterContext()  or  ContextImpl -> mOuterContext
        Class<?> contextImplCls = Class.forName("android.app.ContextImpl");
        Field mOuterContextField = contextImplCls.getDeclaredField("mOuterContext");
        mOuterContextField.setAccessible(true);
        mOuterContextField.set(mBase, realApplication);

        //2.ActivityThread ->mAllApplications.add()
        //ActivityThread 可以通过 ContextImpl 的 mMainThread 属性获得
        Field activityThreadField = contextImplCls.getDeclaredField("mMainThread");
        activityThreadField.setAccessible(true);
        Object mMainThreadIns = activityThreadField.get(mBase);

        Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
        Field mAllApplicationsFiled = activityThreadCls.getDeclaredField("mAllApplications");
        mAllApplicationsFiled.setAccessible(true);
        ArrayList<Application> mAllApplications = (ArrayList<Application>) mAllApplicationsFiled.get(mMainThreadIns);
        mAllApplications.remove(this);
        mAllApplications.add(realApplication);

        //3.ActivityThread -> mInitialApplication
        Field mInitialApplicationField = activityThreadCls.getDeclaredField("mInitialApplication");
        mInitialApplicationField.setAccessible(true);
        mInitialApplicationField.set(mMainThreadIns, realApplication);

        //4.LoadedApk -> mApplication
        //LoadedApk 可以通过 ContextImpl 的 mPackageInfo 属性获得
        Field mPackageInfoFiled = contextImplCls.getDeclaredField("mPackageInfo");
        mPackageInfoFiled.setAccessible(true);
        Object loadedApkIns = mPackageInfoFiled.get(mBase);

        Class<?> loadApkCls = Class.forName("android.app.LoadedApk");
        Field mApplicationFiled = loadApkCls.getDeclaredField("mApplication");
        mApplicationFiled.setAccessible(true);
        mApplicationFiled.set(loadedApkIns, realApplication);

        Field mApplicationInfoField = loadApkCls.getDeclaredField("mApplicationInfo");
        mApplicationInfoField.setAccessible(true);
        ApplicationInfo mAllApplicationInfo = (ApplicationInfo) mApplicationInfoField.get(loadedApkIns);
        mAllApplicationInfo.className = className;


        //5.ActivityThread -> installContentProviders()参数是来自binder 获取不到 通过反射直接调用不太现实(ContentProvider创建的时
        //通过重载getPackageName(),createPackageContext()方法

        //6.ActivityThread -> mInitialApplication.onCreate()
        realApplication.onCreate();
        isBind = true;
    }

    //创建ContentProvider调用， 但是也有可能没有注册ContentProvider
    @Override
    public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException {
        if (TextUtils.isEmpty(className)) {
            return super.createPackageContext(packageName, flags);
        }
        try {
            bindApplicationContext();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return realApplication;
    }


    @Override
    public String getPackageName() {
        //让ContentProvider创建的时候使用的上下文 在ActivityThread中的installProvider函数
        //命中else
        if (!TextUtils.isEmpty(className)) {
            return "";
        }
        return super.getPackageName();
    }


    //Application创建后执行的第一个方法
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        //ManiFest中配置了MetaData  app_name属性 val是用户Application的全类名
        getDataFromMetaData();
        Log.i("-----", "guard " + isGuard);
        if (isGuard) {//在onCreate()中执行也可以，必须早于Activity的创建
            MultiDex.install(this);
        }
    }



    private void getDataFromMetaData() {
        try {
            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = appInfo.metaData;
            if (metaData != null) {
                if (metaData.containsKey("app_name")) {
                    className = metaData.getString("app_name");
                }
                if (metaData.containsKey("guard_enable")) {
                    isGuard = metaData.getBoolean("guard_enable");
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }



}


